// Changement : on exporte que les donnees, c'est povray qui fabriquera ses objets
// On ne dessine qu'au niveau inferieur

import ij.ImagePlus;
import ij.plugin.PlugIn;
import ij.process.ColorProcessor;
import ij.process.ImageProcessor;


import java.awt.Color;


import java.awt.Font;

import java.awt.Polygon;
import java.io.PrintStream;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Locale;
import java.util.Random; 
import java.util.TreeSet;



import pluginUtils.AGCircle;
import pluginUtils.Circle;
import pluginUtils.CircleComparator;
import pluginUtils.CircleInversion;
import pluginUtils.Complexe; 
import pluginUtils.MobiusTransform;
import pluginUtils.Point2D;
import pluginUtils.SoddyTriplet;
import pluginUtils.AGCircleComparator;



public class ApollonianGasketV7MobiusFull extends ImagePlus implements PlugIn{
	{Locale.setDefault(Locale.US); }
	
	private ImageProcessor primeIP; 

	private int X=10000,Y=10000; // image size
	private int maxDepth=2; 

	private PrintStream output,outputBlender;
	private double xShift=0; 
	private double zShift=0; 
	
	//private TreeSet<AGCircle> enAttente=new TreeSet<AGCircle>(new CircleComparator()); 
	private double decale=4.5; 
	private CircleInversion cinv=new CircleInversion(new Circle(Complexe.createComplexeFromRI(3.5,0.5),0.005)); 
 


	private double minRad=0.0004; //0.0003125*3; //0.0003125; 


	private int compte=0,oldcompte=0; 
	

	private Random generator=new Random(); // One always needs some randomness
	
	// For the scaling of the image
	private double x0=0,x1=1,y0=0,y1=1;
	private double acoef,ccoef,vcoef,wcoef; 
	private double rayon=1.5; // radius of big circle
	private int level=0; 
	
	private String[] palette={"Yellow","Red","Green","Blue","Orange","NavyBlue","Turquoise","Orange"};
	private Color[] palette1={Color.BLUE,Color.RED,Color.YELLOW,Color.BLUE,Color.GREEN,Color.YELLOW};
	public void setCoefs(){
		acoef=X/(x1-x0); 
		vcoef=Y/(y1-y0); 
		ccoef=-acoef*x0; 
		wcoef=-vcoef*y0; 
	}
	 
	
	
	public void setBounds(double xl,double xu,double yl,double yu){
		x0=xl; 
		x1=xu;
		y0=yl;
		y1=yu; 
	}
	
	private void printCircleDescription(AGCircle c){
		//if(intersect(c))return; 
		
		//if(c.getRadius()<0.1) return; 
		int intX=(int)(c.getCenter().getReal()*acoef+ccoef);
		int intY=(int)(c.getCenter().getIm()*vcoef+wcoef);
		int intRx=(int)(c.getRadius()*acoef);
		int intRy=(int)(c.getRadius()*vcoef);
		
	
			
			double Xs=c.getCenter().getReal(); 
			double Ys=c.getCenter().getIm(); 
			double rapp=Xs*Xs+Ys*Ys+1; 
			double u=Xs/rapp; 
			double v=Ys/rapp; 
			double w=(Xs*Xs+Ys*Ys)/rapp;
			
			double Xsr=c.getCenter().getReal(); 
			double Ysr=c.getCenter().getIm()+c.getRadius(); 
			double rappr=Xsr*Xsr+Ysr*Ysr+1; 
			double ur=Xsr/rappr; 
			double vr=Ysr/rappr; 
			double wr=(Xsr*Xsr+Ysr*Ysr)/rappr;
		
			double nrad=(u-ur)*(u-ur)+(v-vr)*(v-vr)+(w-wr)*(w-wr);
			//nrad=Math.sqrt(nrad)*1.414; 
			
			String s="sphere{<"+u+","+v+","+w+">,";
			
			
			s+=nrad; 
			s+=" texture{T1} finish{F1}}"; 
			//output.println(s);
		
	}
		
	
	private void drawCircle(AGCircle c,Color cc){
		double xCenter=c.getCenter().getReal(); 
		double yCenter=c.getCenter().getIm(); 
		//if(c.getRadius()<0.001) return ; 
		
		int intX=(int)((xCenter-xShift)*acoef+ccoef);
		int intY=(int)((yCenter-zShift)*vcoef+wcoef);
		int intRx=(int)(c.getRadius()*acoef);
		int intRy=(int)(c.getRadius()*vcoef);
		float cr=c.getColor()/(maxDepth+0.0f); 
		float cg=(float)c.getCenter().getModule();
		float cb=cg*cr; 
		//primeIP.setColor(new Color(cr,Math.min(2*cb,0.5f),Math.min(Math.abs(1.5f-200*cg),1.0f)));
		//primeIP.setColor(new Color(0f,0f,1.0f));
		primeIP.setColor(cc); 
		primeIP.fillOval(intX-intRx,intY-intRy,intRx*2,intRy*2);

	}
		
		
	private void drawPoint(double x,double y){
		int radi=15; 
		int intX=(int)(x*acoef+ccoef);
		int intY=(int)(y*vcoef+wcoef);
		primeIP.fillOval(intX-radi,intY-radi,2*radi,2*radi);
	}
	
	
	
	private void drawTriangle(double[] X,double[] Y){
		int xint[]=new int[3]; 
		int yint[]=new int[3];
		for(int i=0;i<3;i++){
			xint[i]=(int)(X[i]*acoef+ccoef); 
			yint[i]=(int)(Y[i]*vcoef+wcoef);
		}
		primeIP.fillPolygon(new Polygon(xint,yint,3)); 
		
	}
	
	
	
	private void drawLine(double xd,double yd,double xf,double yf){
		int XD=(int)(xd*acoef+ccoef); 
		int YD=(int)(yd*vcoef+wcoef); 
		int XF=(int)(xf*acoef+ccoef); 
		int YF=(int)(yf*vcoef+wcoef);
		primeIP.drawLine(XD,YD,XF,YF); 
	}
	
	
	private void drawLine(Point2D p,Point2D q){
		int XD=(int)(p.getX()*acoef+ccoef); 
		int YD=(int)(p.getY()*vcoef+wcoef); 
		int XF=(int)(q.getX()*acoef+ccoef); 
		int YF=(int)(q.getY()*vcoef+wcoef);
		primeIP.drawLine(XD,YD,XF,YF); 
	}
	
	/** On choisit un cercle, on y place trois cercles, et on lance l'algo **/
	private void makeGasket(AGCircle origin,int nb,int level){
		double cx=origin.getXcenter()-decale; 
		double cy=origin.getYcenter()-decale;
		int nb0=level+3; 
		double nrm; 
			double dist=Math.sqrt(cx*cx+cy*cy)/1.5; // entre 0 et 1
			double kk=dist*(1-dist); 
			
			
			if((cy==0)&&(cx==0)) nrm=0; 
			else{
			nrm=(Math.atan2(cy,cx)+Math.PI)/(2*Math.PI); // entre -1 et 1
			nrm=(nrm+1)/2; //entre 0 et 1
			//nrm=(1+cx+5*cy)/1.5; 	
			nrm=Math.sqrt(nrm*(1-nrm)); 
			}
		
			double ilev=(level+0.0)/(maxDepth-1.0); 
			//ilev=ilev*(1-ilev);

			nb0=(int)((26*kk+15*nrm)); //(int)((1+2*kk+90*nrm+6*ilev)*20); 

			if(level==0) 
				nb0=3; 
			else
			   nb0=Math.max(3,nb0/2); 
			
		AGCircle newOrigin=new AGCircle(origin.getCenter(),origin.getRadius(),true,level);
		double dx=newOrigin.getXcenter(); 
		double dy=newOrigin.getYcenter(); 
		AGCircle ring[]=new AGCircle[nb0];
		double petitr=newOrigin.getRadius()*(Math.sin(Math.PI/nb0)/(1+Math.sin(Math.PI/nb0)));
		boolean addedRing=false; 
		for(int i=0;i<nb0;i++){
			ring[i]=new AGCircle(Complexe.createComplexeFromRI(dx+(newOrigin.getRadius()-petitr)*Math.cos(2*Math.PI*i/nb0),dy+(newOrigin.getRadius()-petitr)*Math.sin(2*Math.PI*i/nb0 )), petitr,false,level+1);
			
			if(ring[i].getRadius()>=minRad)
			{
			balance.add(ring[i]);
			addedRing=true; 
			//if(level>=maxDepth-3)
			{
			cerclesConnus.add(ring[i]); 
			compte++; 
			}
			}
		}
		AGCircle middle=new AGCircle(Complexe.createComplexeFromRI(dx, dy),newOrigin.getRadius()-2*petitr,false,level);
		if((addedRing)&&(middle.getRadius()>=minRad))
		{
			balance.add(middle); 
			//if(level>=maxDepth-3)
			{
		    cerclesConnus.add(middle);
		    compte++; 
		   }
		}
		ArrayList<SoddyTriplet> myLocalSoddyTriplets=new ArrayList<SoddyTriplet>(); 
		
		for(int i=0;i<nb0;i++){
			myLocalSoddyTriplets.add(new SoddyTriplet(newOrigin, ring[i], ring[(i+1)%nb0])); 
			myLocalSoddyTriplets.add(new SoddyTriplet(ring[i], ring[(i+1)%nb0], middle));
		}
		int levelApp=20; 
		while((levelApp!=0)&&(myLocalSoddyTriplets.size()!=0)){
		ArrayList<SoddyTriplet> pro=new ArrayList<SoddyTriplet>();
		for(SoddyTriplet toto:myLocalSoddyTriplets){
			AGCircle nouvo=toto.getMini();
			if((nouvo.getRadius()>=minRad)&&(nouvo.getRadius()<3)){
				//if(level>=maxDepth-3)
				{
				
				cerclesConnus.add(nouvo);
				compte++; 
				}
				if(compte-oldcompte>=100000)
				//if(compte%1000==0)
				{ 
			
				oldcompte=compte; 
				System.out.println("** "+compte);  
				}
				//System.out.println(compte+" "+ level); }
				
				balance.add(nouvo); 
				AGCircle a=toto.getA(); 
				AGCircle b=toto.getB(); 
				AGCircle c=toto.getC();

			    pro.add(new SoddyTriplet(a,b,nouvo)); 
				pro.add(new SoddyTriplet(a,c,nouvo));
			    pro.add(new SoddyTriplet(c,b,nouvo));		
			} //if
			
		}
		levelApp--;
		myLocalSoddyTriplets=pro; 
		//System.out.println(pro.size());
		} // while
	}
	
	private ArrayList<AGCircle> aTraiter=new ArrayList<AGCircle>();
	private ArrayList<AGCircle> balance=new ArrayList<AGCircle>(); 
	private ArrayList<AGCircle> cerclesConnus=new ArrayList<AGCircle>(); 
	
	public void launch(){

		for(int i=0;i<maxDepth;i++){

			System.out.println(i+" "+aTraiter.size()+" "+compte); //+" "+enAttente.size()); 
			balance=new ArrayList<AGCircle>(); 
			for(AGCircle toto:aTraiter){
				Random gene=new Random(); 
				makeGasket(toto,3,i); 
			}
			aTraiter=balance; 
		}
	}
	
	public void run(String arg) {
		primeIP = new ColorProcessor(X,Y);
		

		// T=kz
		Complexe aComplex=Complexe.createComplexeFromRI(-1,-2);
		Complexe bComplex=Complexe.createComplexeFromRI(0.1,0.87); 
		Complexe c=Complexe.createComplexeFromRI(0.2,-0.4);
		Complexe d=Complexe.createComplexeFromRI(-10,-0.01); 
	
		MobiusTransform T=new MobiusTransform(aComplex,bComplex,c,d); 
		System.out.println("***\n"+T+"\n***");
		System.out.println("***\n"+T.inverse()+"\n***");
		
		// Conjugating with R
		Complexe ar=Complexe.createComplexeFromRI(0.5,1.732);
		Complexe br=Complexe.createComplexeFromRI(1.414,0.02); 
		Complexe cr=Complexe.createComplexeFromRI(1,20);
		Complexe dr=Complexe.createComplexeFromRI(Math.sqrt(2),4); 
		MobiusTransform R=new MobiusTransform(ar,br,cr,dr);
		MobiusTransform Rinv=R.inverse(); 
		//System.out.println("***\n"+R+"\n Inverse :\n"+Rinv);
		
		MobiusTransform TP=R.mul(T.inverse()); 
		T=T.mul(TP); 
		
	
		double limit=6.1; 
		double lmoins=2.9;
		
		
		//setBounds(lmoins,limit,lmoins,limit);
		setBounds(-0.22,-0.13,1.05,1.14); 
		setCoefs(); 
	
		Polygon background=new Polygon(new int[]{0,0,X,Y},new int[]{0,X,Y,0},4);
		primeIP.setColor(Color.WHITE);
		
		primeIP.fillPolygon(background);
		
		
		primeIP.setFont(new Font(null,Font.ITALIC,60));
		primeIP.setColor(Color.BLACK);
		primeIP.setLineWidth(1); 
		

		setProcessor("Verifying", primeIP);
		
	
		//primeIP.setColor(Color.BLACK);
		primeIP.setLineWidth(1);
	
		//primeIP.setColor(Color.BLACK);
		show();
		
	 
		AGCircle init=new AGCircle(Complexe.createComplexeFromRI(decale,decale),rayon,true,0); 
		aTraiter.add(init); 
		cerclesConnus.add(init); 
		try{
			//output=new PrintStream("F:/Povray/outApolloGasket.txt");
			//outputBlender=new PrintStream("F:/Povray/outApolloGasketBlender.txt");
			//output=new PrintStream("C:/users/decomite/pictures/outApollo4xx.txt"); 
			output=new PrintStream("/tmp/apollo.txt"); 
			outputBlender=new PrintStream("/tmp/outApolloBlender.txt"); 
			}
			catch(Exception e){System.out.println("Probleme 2 "+e); System.exit(0);}
		launch();
		/*
		try{
			//output=new PrintStream("F:/Povray/outApollox4xx.txt");
			//output=new PrintStream("C:/users/decomite/pictures/outApollo4.txt"); 
			}
			catch(Exception e){System.out.println("Probleme 2 "+e); System.exit(0);}
			*/
			/*
		for(AGCircle ccer:enAttente){
			AGCircle ci=ccer; 
			//AGCircle ci=cinv.inverte(ccer); 
			
			drawCircle(ci);
			//output.print((ci.getXcenter()-decale)+","+(ci.getYcenter()-decale)+","+ci.getRadius()+","+ci.getColor()+"\n");
			output.print((ci.getXcenter()-decale)+","+(ci.getYcenter()-decale)+","+ci.getRadius()+","+ci.getColor()+",");
			
			//System.out.println((ci.getXcenter()-decale)+","+(ci.getYcenter()-decale)+","+ci.getRadius()+","+ci.getColor()+"\n"); 
			
		}
		*/
	
		
		double minX=50000; 
		double maxX=-50000; 
		double minY=50000; 
		double maxY=-50000; 
		double maxRado=-50000; 
		double minRado=50000; 
		int ccoun=1; 
		ArrayList<AGCircle> cerclesTrans=new ArrayList<AGCircle>();
		
		for(AGCircle cc:cerclesConnus){
			if(ccoun%10000==0){
			// Redefinir la transfo de Mobius
			// T=kz
		 aComplex=Complexe.createComplexeFromRI(-1+(generator.nextDouble()-0.5),-2+(generator.nextDouble()-0.5));
			bComplex=Complexe.createComplexeFromRI(0.1+(generator.nextDouble()-0.5),0.87+(generator.nextDouble()-0.5)); 
			c=Complexe.createComplexeFromRI(0.2+(generator.nextDouble()-0.5),-0.4+(generator.nextDouble()-0.5));
			d=Complexe.createComplexeFromRI(-10+(generator.nextDouble()-0.5),-0.01+(generator.nextDouble()-0.5)); 
		
		  T=new MobiusTransform(aComplex,bComplex,c,d); 
		//	System.out.println("***\n"+T+"\n***");
		//	System.out.println("***\n"+T.inverse()+"\n***");
			
			// Conjugating with R
			ar=Complexe.createComplexeFromRI(0.5+(generator.nextDouble()-0.5),1.732+(generator.nextDouble()-0.5));
			br=Complexe.createComplexeFromRI(1.414+(generator.nextDouble()-0.5),0.02+(generator.nextDouble()-0.5)); 
		 cr=Complexe.createComplexeFromRI(1+(generator.nextDouble()-0.5),20+(generator.nextDouble()-0.5));
			 dr=Complexe.createComplexeFromRI(Math.sqrt(2),4+(generator.nextDouble()-0.5)); 
			 R=new MobiusTransform(ar,br,cr,dr);
			Rinv=R.inverse(); 
			//System.out.println("***\n"+R+"\n Inverse :\n"+Rinv);
			
			TP=R.mul(T.inverse()); 
			T=T.mul(TP); 
			
			
			
			
			// fin redef
			
		} // if ccoun
		ccoun++; 
			//AGCircle v=T.circleImage(cc); //cinv.inverte(cc);
			AGCircle v=T.circleImage(cinv.inverte(T.circleImage(cc)));
			//AGCircle v=cc; //cinv.inverte(cc); //cinv.inverte(T.circleImage(T.circleImage(cinv.inverte(cc))));
			cerclesTrans.add(v);
			if(cerclesTrans.size()%100000==0)
				System.out.println(cerclesConnus.size()+" "+cerclesTrans.size());
			Complexe cent=v.getCenter(); 
			Double ra=v.getRadius(); 
			if(cent.getReal()-ra<minX) minX=cent.getReal()-ra; 
			if(cent.getReal()+ra>maxX) maxX=cent.getReal()+ra; 
			if(cent.getIm()-ra<minY) minY=cent.getIm()-ra; 
			if(cent.getIm()+ra>maxY) maxY=cent.getIm()+ra; 
			if(ra<minRado) minRado=ra; 
			if(ra>maxRado) maxRado=ra; 
		}
		Collections.sort(cerclesTrans,new AGCircleComparator()); 
		System.out.println(minX+" "+maxX+" "+minY+" "+maxY);
			double eps=(maxX-minX)/50; 
			double decaleX=(maxX+minX)/2;
			double decaleY=(maxY+minY)/2; 
			setBounds(minX-eps-decaleX,maxX+eps-decaleX,minY-eps-decaleY,maxY+eps-decaleY); 
			System.out.println(minX+","+maxX+","+minY+","+maxY); 
			System.out.println((maxX-minX)+" "+(maxY-minY));
			System.out.println("Minrado et maxrado sont dans un bato "+minRado+" "+maxRado); 
			double radmin=(maxX-minX)/100000.0; 
			System.out.println("radmin ->"+radmin);
			
			setCoefs(); 
			System.out.println("pas fini "+cerclesConnus.size()); 
			int count=0; 
			primeIP.setLineWidth(1);
			Collections.sort(cerclesTrans,new AGCircleComparator()); 
		for(AGCircle cc:cerclesTrans) {
			AGCircle nouvo=cc;
			if(nouvo.getRadius()/maxRado>=radmin){
				nouvo.setXCenter(nouvo.getXcenter()-decaleX); 
				nouvo.setYCenter(nouvo.getYcenter()-decaleY); 
				
			drawCircle(nouvo ,new Color(
					0.2f*(float)Math.abs(Math.sin(4*Math.PI*(count+0.0f)/cerclesTrans.size())), 
					0.7f*(float)Math.abs(Math.sin(3*Math.PI*(count+0.0f)/cerclesTrans.size())),
					(float)Math.abs(Math.cos(2*Math.PI*(count+0.0f)/cerclesTrans.size())))
			);// Color
			count++; 
			if(count%100000==0) updateAndDraw();
			 //output.print((nouvo.getXcenter())+","+(nouvo.getYcenter())+","+nouvo.getRadius()+","+nouvo.getColor()+","+(level+1.0)/maxDepth+",");
			 //output.print((nouvo.getXcenter()/maxRado)+","+(nouvo.getYcenter()/maxRado)+","+(nouvo.getRadius()/maxRado)+",");
			 //outputBlender.println((nouvo.getXcenter()/maxRado)+" "+(nouvo.getYcenter()/maxRado)+" "+(nouvo.getRadius()/maxRado)); 
			}
		}
		System.out.println("fini "+cerclesTrans.size()+" "+count); 
		

		/*

		for(AGCircle w: cerclesTrans) {
			if(compte<30000)
			{
				drawCircle(w);
				output.format("var u=new Vector3d(Rout*%.3f ,Rout*%.3f,0);\n",w.getCenter().getReal()/maxRado,w.getCenter().getIm()/maxRado,w.getRadius()/maxRado);
				output.format("var totor=new Torus(u,Rout*%.3f,Rin);\n",w.getRadius()/maxRado);
				output.println("union.add(totor);"); 
				System.out.format("%.3f , %.3f , %.3f\n",w.getCenter().getReal()/maxRado,w.getCenter().getIm()/maxRado,w.getRadius()/maxRado);
			//System.out.println(w);
				compte++; 
			}
		}
		*/
		updateAndDraw(); 
		output.close();
		System.out.println("taille "+cerclesTrans.size());
	}	
}


